import { gyColor2rgb255str } from "./color";
import { PropertyItem } from "./property";

// 侧边栏中组件相关的数据结构定义，相关处理函数。
// 后端返回的bomInfo是以字符串作为id的，而三维渲染场景中的id是数字，并且后端接收entity赋材料的id也是number。
// GeoItem中的id和parentId都用string，在记录模型隐藏和赋值材料的场景，都用number。
interface GeoItem {
  id: string;
  parent: string | number;
  label: string;
  expanded?: boolean;
  show: boolean;
  children: GeoItem[];
  colorStr: string;
  prop?: PropertyItem;
}

export class EntityPropRecorder {
  private entityId2prop: Map<number, PropertyItem> = new Map();
  private propId2entityIds: Map<number, number[]> = new Map();

  record(entity_id: number, prop: PropertyItem) {
    // 反向记录 prop -> entity_ids
    // 1. 先移除原有的记录
    const origin_prop = this.entityId2prop.get(entity_id);
    if (origin_prop) {
      this.removePropEntityId(origin_prop.id, entity_id);
    }
    // 2. 新增记录
    this.addPropEntityId(prop.id, entity_id);

    // 正向记录 entity_id -> prop
    this.entityId2prop.set(entity_id, prop);
  }

  getProp(entity_id: number | string) {
    if (typeof entity_id == "string") {
      entity_id = parseInt(entity_id);
    }
    return this.entityId2prop.get(entity_id);
  }

  addPropEntityId(propId: number, entityId: number) {
    let entityIds = this.propId2entityIds.get(propId);
    if (!entityIds) {
      entityIds = [];
      this.propId2entityIds.set(propId, entityIds);
    }
    entityIds.push(entityId);
  }

  removePropEntityId(propId: number, entityId: number) {
    const entityIds = this.propId2entityIds.get(propId);
    if (!entityIds) {
      return;
    }
    const newEntityIds = entityIds.filter((item) => item !== entityId);
    this.propId2entityIds.set(propId, newEntityIds);
  }

  getEntityIds(propId: number) {
    const entityIds = this.propId2entityIds.get(propId);
    if (!entityIds) return [];
    return entityIds;
  }
}

// 获取所有的层级（包括子组件）的id，注意非数字的id不会被添加进入返回结果中。
function getEntityIds(data: GeoItem) {
  const entityIds: number[] = [];
  const itemList: GeoItem[] = [data];
  // 此处不能用!itemList。
  while (itemList.length > 0) {
    const item = itemList.pop();
    if (!item) continue;
    const id = item.id;
    if (typeof id == "string") {
      const idResult = parseInt(id);
      if (!isNaN(idResult)) {
        entityIds.push(idResult);
      }
    } else {
      entityIds.push(id);
    }

    itemList.push(...item.children);
  }
  return entityIds;
}

function getEntityPropItem(
  entity2prop: Map<number, PropertyItem>,
  entity_id: number | string
) {
  if (typeof entity_id == "string") {
    entity_id = parseInt(entity_id);
  }
  const propItem = entity2prop.get(entity_id);
  return propItem;
}

// 比较bomInfo是否想等，相等就不要刷新了。
function deepEqual(obj1: any, obj2: any): boolean {
  if (obj1 === obj2) return true; // 处理相同引用或值相等的情况
  if (obj1 === null || obj2 === null) return false; // 处理一个为 null 的情况
  if (typeof obj1 !== "object" || typeof obj2 !== "object") return false; // 处理非对象的情况
  if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; // 处理属性个数不等的情况

  for (const key of Object.keys(obj1)) {
    // eslint-disable-next-line no-prototype-builtins
    if (!obj2.hasOwnProperty(key)) return false; // 处理 obj2 中没有的属性
    if (!deepEqual(obj1[key], obj2[key])) return false; // 递归比较属性值
  }

  return true;
}

class IdSet {
  ids: Set<number | string> = new Set();
  constructor() {
    this.ids.add(TREE_ROOT);
  }

  addGeoItemId(id: number | string) {
    if (this.ids.has(id)) {
      alert("error: bomInfo中出现重复的id！");
    } else {
      this.ids.add(id);
    }
  }
}

const TREE_ROOT = 0;
// 此方法将bomInfo转为模型树的data数据。
// 1. id不能是undefined
// 2. parent是父元素的id
// 3. 父元素和子元素的id不同
// 4. assembly是没有id的，但是有groupkey，所以自定义了一个gp_<groupkey>。
// 5. 如果元素id以gp_开头，那么不对属性和隐藏进行设置。
// 6. root元素的parent值为0，否则Tree组件无法对数据进行识别。
// 7. TreeData中应该可以同时存在值相同的string和number吧？"1"和1。
function parseBomInfo(
  entityPropRecorder: EntityPropRecorder,
  bomInfo: any,
  parent: string | number,
  expanded = true,
  idSet: IdSet = new IdSet()
) {
  const data: GeoItem[] = [];
  let rootId = "gp_" + bomInfo.groupKey;
  if (bomInfo.id) {
    rootId = bomInfo.id;
  }
  idSet.addGeoItemId(rootId);
  const rootChildren: GeoItem[] = [];
  const root = {
    id: rootId,
    label: bomInfo.name,
    parent: parent,
    expanded: expanded,
    show: true,
    children: rootChildren,
    colorStr: "rgb(255, 255, 0)",
    prop: entityPropRecorder.getProp(rootId),
  };
  data.push(root);
  if (bomInfo.bodies) {
    for (const item of bomInfo.bodies) {
      const bodyChildren: GeoItem[] = [];
      const body = {
        id: item.id,
        label: item.name,
        parent: root.id,
        show: true,
        children: bodyChildren,
        colorStr: "rgb(255, 255, 0)",
        prop: entityPropRecorder.getProp(item.id),
      };
      idSet.addGeoItemId(body.id);
      data.push(body);
      root.children.push(body);
      if (item.solids) {
        for (let i = 0; i < item.solids.length; i++) {
          const solid = {
            id: item.solids[i],
            label: item.solidsNames[i],
            parent: body.id,
            show: true,
            children: [],
            colorStr: "rgb(255, 255, 0)",
            prop: entityPropRecorder.getProp(item.solids[i]),
          };
          idSet.addGeoItemId(solid.id);
          data.push(solid);
          body.children.push(solid);
        }
      }
    }
  }

  if (!bomInfo.assemblies) return data;
  for (const item of bomInfo.assemblies) {
    const children = parseBomInfo(
      entityPropRecorder,
      item,
      root.id,
      true,
      idSet
    );
    root.children.push(children[0]);
    for (const child of children) {
      data.push(child);
    }
  }
  return data;
}

// 在parent这个GeomItem中，还它的子节点中，查找id对应的GeoItem
function getGeoItemById(data: GeoItem, id: any) {
  const itemList: GeoItem[] = [data];
  while (itemList.length > 0) {
    const item = itemList.pop();
    if (!item) continue;
    if (item.id == id) {
      return item;
    }
    if (typeof item.id == "string" && parseInt(item.id) == id) {
      return item;
    }
    itemList.push(...item.children);
  }
  return null;
}

// 模型树相关
// 获取GeoItem的颜色
function getGeoItemColor(data: any) {
  if (!data.prop || data.prop == null) {
    return "#f9fbfc"; // 默认色为黄色。
  }
  if (data.prop.value.color) {
    return gyColor2rgb255str(data.prop.value.color); // tmp color
  } else {
    return "rgb(255, 0, 0)";
  }
}

export {
  GeoItem,
  getEntityIds,
  parseBomInfo,
  getGeoItemById,
  getGeoItemColor,
  deepEqual,
  getEntityPropItem,
  TREE_ROOT,
};
